package cn.mdmbct.douyin.common.executor;

import cn.mdmbct.douyin.common.bean.AccessToken;
import cn.mdmbct.douyin.common.bean.ClientToken;
import cn.mdmbct.douyin.common.bean.RefreshToken;
import cn.mdmbct.douyin.common.config.DyOpenApiConfig;
import cn.mdmbct.douyin.common.enums.ApiPlatform;
import cn.mdmbct.douyin.common.enums.Auth2;
import cn.mdmbct.douyin.common.enums.ErrorCode;
import cn.mdmbct.douyin.common.exception.ApiAuthFailedException;
import cn.mdmbct.douyin.common.exception.ApiCallException;
import cn.mdmbct.douyin.common.exception.LandExpirationException;
import cn.mdmbct.douyin.common.http.HttpExecutor;
import cn.mdmbct.douyin.common.response.auth.AccessTokenRes;
import cn.mdmbct.douyin.common.response.auth.ClientTokenRes;
import cn.mdmbct.douyin.common.response.auth.RenewRefreshTokenRes;
import cn.mdmbct.douyin.common.session.DySession;
import cn.mdmbct.douyin.common.utils.ApiResultHandler;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;

/**
 * 认证
 *
 * @author mdmbct  mdmbct@outlook.com
 * @date 2021/4/19 22:26
 * @modified mdmbct
 * @since 1.0
 */
@Slf4j
public class DyAuthService {

    protected final DySession dySession;

    protected final HttpExecutor httpExecutor;

    public DyAuthService(DySession dySession) {
        this.dySession = dySession;
        this.httpExecutor = dySession.getHttpExecutor();
    }

    /**
     * 获取用户授权第三方接口调用的凭证access_token；该接口适用于抖音/头条授权。
     *
     * @param code        授权码
     * @param apiPlatform 抖音/头条
     * @return {@link AccessTokenRes.AccessTokenResData }
     * @throws ApiCallException 接口调用异常
     */
    public AccessTokenRes.AccessTokenResData authAccessToken(String code, ApiPlatform apiPlatform) throws ApiCallException {

        // 格式化url
        String url = formatAuthUrl(Auth2.OAUTH_ACCESS_TOKEN, dySession, apiPlatform,
                // args
                dySession.getOpenApiConfig().getApp().getKey(),
                dySession.getOpenApiConfig().getApp().getSecret(),
                code
        );

        String result = httpExecutor.executeGet(null, url);

        return getAndSaveTokenFromAuthApiResponse(JSONObject.parseObject(result, AccessTokenRes.class));
    }

    /**
     * 用RefreshToken请求AccessToken和新的RefreshToken，并将新的token存起来
     *
     * @param refreshToken 旧的RefreshToken
     * @param apiPlatform  调用的授权api所属平台 {@link ApiPlatform}
     * @return {@link AccessTokenRes.AccessTokenResData}
     */
    public AccessTokenRes.AccessTokenResData requestAccessTokenByRefreshToken(RefreshToken refreshToken, ApiPlatform apiPlatform) throws ApiCallException {

        // 格式化url
        String url = formatAuthUrl(Auth2.OAUTH_REFRESH_TOKEN, dySession, apiPlatform,
                dySession.getOpenApiConfig().getApp().getKey(),
                "refresh_token",
                refreshToken.getValue()
        );

        String result = httpExecutor.executeGet(null, url);

        return getAndSaveTokenFromAuthApiResponse(JSONObject.parseObject(result, AccessTokenRes.class));
    }

    /**
     * 从授权API中获取AccessToken和RefreshToken并保存
     *
     * @param accessTokenRes@return AccessTokenResData 包含 AccessToken 和 RefreshToken
     */
    private AccessTokenRes.AccessTokenResData getAndSaveTokenFromAuthApiResponse(AccessTokenRes accessTokenRes) throws ApiAuthFailedException {

        AccessTokenRes.AccessTokenResData data = accessTokenRes.getData();

        if (data.getErrorCode() == ErrorCode.SUCCESS.getValue()) {
            saveToken(data);
            return data;
        }

        log.error(accessTokenRes.getErrorMsg());
        throw new ApiAuthFailedException(accessTokenRes.getErrorMsg());
    }

    public AccessTokenRes.AccessTokenResData authExchangeAccessToken(String openId, ApiPlatform apiPlatform) throws ApiCallException {

        RefreshToken refreshToken = dySession.getRefreshToken(openId);
        if (refreshToken == null) {
            log.error(LandExpirationException.DEFAULT_MSG);
            throw new LandExpirationException();
        }
        return requestAccessTokenByRefreshToken(refreshToken, apiPlatform);
    }


    ////////////////////////////////////////////////////// refresh token //////////////////////////////////////////////////////


    public RenewRefreshTokenRes.RenewRefreshTokenResData renewRefreshToken(String openId) throws ApiCallException {

        RefreshToken oldRefreshToken = dySession.getRefreshToken(openId);
        if (oldRefreshToken == null) {
            throw new LandExpirationException();
        }

        return requestRefreshTokenByOld(oldRefreshToken, openId);
    }

    /**
     * 使用旧refreshToken刷新refreshToken
     *
     * @param oldRefreshToken 旧的refreshToken
     * @param openId          open id
     * @return {@link RenewRefreshTokenRes.RenewRefreshTokenResData}
     */
    public RenewRefreshTokenRes.RenewRefreshTokenResData requestRefreshTokenByOld(RefreshToken oldRefreshToken, String openId) throws ApiCallException {

        // 格式化url
        String url = formatAuthUrl(Auth2.OAUTH_RENEW_REFRESH_TOKEN, dySession, ApiPlatform.DOU_YIN,
                // args
                dySession.getOpenApiConfig().getApp().getKey(),
                oldRefreshToken.getValue());

        String result = httpExecutor.executeGet(null, url);
        RenewRefreshTokenRes renewRefreshTokenRes = JSONObject.parseObject(result, RenewRefreshTokenRes.class);

        RenewRefreshTokenRes.RenewRefreshTokenResData data = renewRefreshTokenRes.getData();
        if (data.getErrorCode() == ErrorCode.SUCCESS.getValue()) {
            RefreshToken refreshToken = new RefreshToken(System.currentTimeMillis(), data.getExpiresIn() * 1000L, data.getRefreshToken());
            dySession.saveRefreshToken(openId, refreshToken);
            return data;
        }

        log.error(renewRefreshTokenRes.getErrorMsg());
        throw new ApiAuthFailedException(renewRefreshTokenRes.getErrorMsg());
    }


    ////////////////////////////////////////////////////// client token //////////////////////////////////////////////////////

    public ClientTokenRes.ClientTokenResData authClientToken(ApiPlatform platform) throws ApiCallException {

        DyOpenApiConfig apiConfig = dySession.getOpenApiConfig();

        // 格式化url
        String url = formatAuthUrl(Auth2.OAUTH_CLIENT_TOKEN, dySession, platform,
                // args
                apiConfig.getApp().getKey(),
                apiConfig.getApp().getSecret());
        String result = httpExecutor.executeGet(null, url);
        ClientTokenRes.ClientTokenResData data = ApiResultHandler.handleApiResponseData(result, ClientTokenRes.class);
        dySession.saveClientToken(new ClientToken(data.getExpiresIn() * 1000L, data.getAccessToken()));
        return data;
    }


    ////////////////////////////////////////////////////// private //////////////////////////////////////////////////////

    private void saveToken(AccessTokenRes.AccessTokenResData data) {
        // 保存AccessToken和RefreshToken
        long currentTimeMillis = System.currentTimeMillis();
        dySession.saveAccessToken(data.getOpenId(), new AccessToken(currentTimeMillis, data.getExpiresIn() * 1000L, data.getAccessToken()));
        dySession.saveRefreshToken(data.getOpenId(), new RefreshToken(currentTimeMillis, data.getRefreshExpiresIn() * 1000L, data.getRefreshToken()));
    }


    private String formatAuthUrl(Auth2 authApi,
                                 DySession storageManager,
                                 ApiPlatform platform,
                                 Object... args) {
        return String.format(authApi.getUrl(storageManager, platform), args);
    }

}
